package com.douban.auth
{
	import com.adobe.serialization.json.JSON;
	import com.douban.event.AuthenticateEvent;
	import com.douban.event.DBErrorEvent;
	
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.HTTPStatusEvent;
	import flash.events.IOErrorEvent;
	import flash.events.ProgressEvent;
	import flash.events.SecurityErrorEvent;
	import flash.net.SharedObject;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.net.URLRequestMethod;
	import flash.net.URLVariables;
	
	import net.sevear.chaos.DBRadioChannelParser;
	import net.sevear.chaos.download.Predef;
	import net.sevear.chaos.event.ContactEvent;
	import net.sevear.chaos.recommend.ContactManager;
	import net.sevear.chaos.utils.Logger;
	import net.sevear.chaos.vo.UserInfo;
	
	public class DBAuthenticate extends EventDispatcher
	{
		public static const LOGIN_APP_TYPE:int = 0;
		public static const LOGIN_WEB_TYPE:int = 1;
		
		private var _type:int = LOGIN_APP_TYPE;
		private var _appToken:String;
		private var _appExpire:String;
		
		private static const UN_LOGIN:int = -1;
		private static const LOGIN_STEP1:int = 0;
		private static const LOGIN_STEP2:int = 1;
		private static const LOGIN_COMPLETE:int = 2;
		private static const LOGIN_APP:int = 3;
		
		private static const LOGIN_URL:String = "https://www.douban.com/accounts/login?source=radio";
		private static const LOGIN_URL3:String = "/login";
		private static const MID_COOKIE_URL:String = "http://img3.douban.com/js/lib/packed_hotkeys4.js";
		
		private static var _instance:DBAuthenticate;
		
		private var loader:URLLoader;
		private var rst:URLRequest;
		private var isSendingRequest:Boolean = false;
		private var stepIdx:int = -1;
		private var _err:String="";
		
		private var so:SharedObject;
		private var _dbName:String;
		private var _dbUID:int;
		private var _dbScreenName:String;
		private var _imgURL:String;
		private var _email:String;
		private var _pwd:String;
		private var _auto:Boolean;
		public function DBAuthenticate()
		{
			initSO();
			
			if(loader == null){
				loader = new URLLoader();
				loader.addEventListener(Event.COMPLETE,completedHandler);
				loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, secrityErrorHandler);
				loader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler);
				loader.addEventListener(HTTPStatusEvent.HTTP_RESPONSE_STATUS,onHttpStatus);
			}
		}
		
		private function initSO():void{
			this.so = SharedObject.getLocal("douchaos", "/");
			if (this.so.data.email == undefined)
			{
				this.so.data.email = "";
				this.so.flush();
			}else{
				this._email = this.so.data.email;
			}
			
			if(this.so.data.pwd == undefined){
				this.so.data.pwd = "";
				this.so.flush();
			}else{
				this._pwd = this.so.data.pwd;
			}
			
			if(this.so.data.autoLogin == undefined){
				this.so.data.pwd = "false";
				this.so.flush();
			}else{
				this._auto = this.so.data.autoLogin=="true";
			}
		}
		
		public static function getInstance():DBAuthenticate{
			if(_instance==null){
				_instance = new DBAuthenticate();
			}
			
			return _instance;
		}
		
		
		public function get logInType():int{
			return _type;
		}
		
		public function get appToken():String{
			return _appToken;	
		}
		
		public function get appExpire():String{
			return _appExpire;
		}
		
		public function tryLoginApp(email:String,pwd:String,auto:Boolean):void{
			this._email = email;
			this._pwd = pwd;
			this.autoLogin = auto;
			_type = LOGIN_APP_TYPE;
			stepIdx = LOGIN_APP;
			
			var vars:URLVariables = new URLVariables();
			vars.email 		= email;
			vars.password	= pwd;
			vars.app_name  	= "radio_iphone";
			vars.version 	= 20;
			if(rst == null){
				rst = new URLRequest();
				rst.manageCookies = true;
				rst.followRedirects = true;
				rst.url = Predef.DOUBAN_APP_HOST+LOGIN_URL3;
				rst.authenticate = true;
				rst.method = URLRequestMethod.GET;
			}
			
			rst.data = vars;
			loader.load(rst);
		}
		
		public function tryLogin(email:String,pwd:String,auto:Boolean):void{
			this._email = email;
			this._pwd = pwd;
			this.autoLogin = auto;
			
			_type = LOGIN_WEB_TYPE;
			stepIdx = LOGIN_STEP1;
			
			var vars:URLVariables = new URLVariables();
			vars.form_email 	= email;
			vars.form_password	= pwd;
			vars.remember 		= auto;
			
			if(rst == null){
				rst = new URLRequest();
			}
			rst.manageCookies = true;
			rst.followRedirects = true;
			rst.url = LOGIN_URL;
			rst.authenticate = true;
			rst.method = URLRequestMethod.POST;
			rst.data = vars;
			loader.load(rst);
		}
		
		public function tryLoginWithCap(email:String,pwd:String,captcha:String,auto:Boolean = false):void{
			this._email = email;
			this._pwd = pwd;
			this.autoLogin = auto;
			
			stepIdx = LOGIN_STEP1;
			
			var vars:URLVariables = new URLVariables();
			vars.form_email 	= email;
			vars.form_password	= pwd;
			vars.remember 		= auto;
			vars["captcha-solution"] = captcha;
			vars["captcha-id"] = _captchaSrc;
			
			if(rst == null){
				rst = new URLRequest();
			}
			rst.manageCookies = true;
			rst.authenticate = true;
			rst.url = LOGIN_URL;
			rst.method = URLRequestMethod.POST;
			rst.data = vars;
			loader.load(rst);
		}
		
		public function get emailHis():String{
			return this._email;
		}
		
		public function get pwdHis():String{
			return this._pwd;
		}
		
		public function set autoLogin(value:Boolean):void{
			_auto = value;
			this.so.data.autoLogin = _auto.toString();
			this.so.flush();
		}
		
		public function get autoLogin():Boolean{
			return _auto;
		}
		
		public function get userUrl():String{
			return "http://www.douban.com/people/"+_dbScreenName;
		}
		
		public function get dbName():String{
			return _dbName;
		}
		
		public function get dbUID():int{
			return _dbUID;
		}
		
		public function get imgURL():String{
			return _imgURL;
		}
		
		public function get captchaUrl():String{
			return "http://www.douban.com/misc/captcha?id="+_captchaSrc+"&size=s";
		}
		
		public function get dbScreenName():String{
			return _dbScreenName;
		}
		
		private function completedHandler(e:Event):void{
			switch(stepIdx){
				case UN_LOGIN:
					break;
				case LOGIN_STEP1:
					var result:int = checkLoginRst((e.target as URLLoader).data as String);
					if(result == 0){
						loginWeb2();
					}else if(result == -1){
						dispatchEvent(new DBErrorEvent(DBErrorEvent.LOGIN_FAILED,_err));
					}else if(result == -2){
						autoLogin = false;
						dispatchEvent(new AuthenticateEvent(AuthenticateEvent.NEED_CAPTCHA));
					}
					break;
				case LOGIN_STEP2:
					getFMPage();
					if(this.so.data.email!=this._email){
						dispatchEvent(new AuthenticateEvent(AuthenticateEvent.ACCOUNT_CHANGED));
					}
					flushAccountInfo();
					break;
				case LOGIN_COMPLETE:
					var fmHtml:String = (e.target as URLLoader).data as String;
					getuidAndPlayList(fmHtml);
					getCkValue(fmHtml);
					if(ContactManager.getInstance().hasEventListener(ContactEvent.SEARCH_MY_SELF_INFO)==false){
						ContactManager.getInstance().addEventListener(ContactEvent.SEARCH_MY_SELF_INFO,onGetInfo);
					}
					ContactManager.getInstance().searchMyInfo(_dbUID.toString());
					dispatchEvent(new AuthenticateEvent(AuthenticateEvent.LOGIN_SUCCESSFULLY));
					break;
				case LOGIN_APP:
					var appStr:String = (e.currentTarget as URLLoader).data as String;
					var appJson:Object = JSON.decode(appStr);
					if(appJson.r==1 || appJson.err!="ok"){
						_err = getErrFromApp(appJson.err as String);
						autoLogin = false;
						dispatchEvent(new DBErrorEvent(DBErrorEvent.LOGIN_FAILED,_err));
					}else{
						_dbUID = parseInt(appJson.user_id as String);
						_dbName = (appJson.user_name as String);
						_appExpire = appJson.expire;
						_appToken = appJson.token;
						if(ContactManager.getInstance().hasEventListener(ContactEvent.SEARCH_MY_SELF_INFO)==false){
							ContactManager.getInstance().addEventListener(ContactEvent.SEARCH_MY_SELF_INFO,onGetInfo);
						}
						ContactManager.getInstance().searchMyInfo(_dbUID.toString());
						
						dispatchEvent(new AuthenticateEvent(AuthenticateEvent.LOGIN_SUCCESSFULLY));
						flushAccountInfo();
					}
					break;
			}
		}
		
		private function loginWeb2():void{
			stepIdx = LOGIN_STEP2;
			rst = new URLRequest(MID_COOKIE_URL);
			rst.method = URLRequestMethod.GET;
			loader.load(rst);
		}
		
		private function getFMPage():void{
			stepIdx = LOGIN_COMPLETE;
			rst = new URLRequest("http://douban.fm");
			rst.method = URLRequestMethod.GET;
			loader.load(rst);
		}
		
		private function flushAccountInfo():void{
			this.so.data.email = this._email;
			this.so.data.pwd = this._pwd;
			this.so.flush();
		}
		
		private function onGetInfo(e:ContactEvent):void{
			var rstInfo:UserInfo = e.data as UserInfo;
			_dbName = rstInfo.dbName;
			_dbUID = rstInfo.dbID;
			_dbScreenName = rstInfo.dbUID;
			_imgURL = rstInfo.imgUrl;
			dispatchEvent(new AuthenticateEvent(AuthenticateEvent.GOTTEN_DOUBAN_INFO));
		}
		private function getErrFromApp(jsonErr:String):String{
			switch(jsonErr){
				case "wrong_password":
					return "密码或者用户名错误";
				default:
					return "未知错误";
			}
		}
		
		private function getuidAndPlayList(html:String):void{
			var flashVarsStr:String = "";
			var rst:Array= html.match(/flashvars[ \b\n]*=[ ]*{[ \n\rA-Za-z:'0-9%,\/.\[\]|_+]*/g);
			if(rst!=null && rst.length>0){
				var uidMatch:Array = (rst[0] as String).match(/uid:[ ]*'[0-9]*'/g);
				if(uidMatch!=null && uidMatch.length>0){
					var tmp:String = uidMatch[0] as String;
					//FIXME:正则没有匹配出uid
					var uids:String = tmp.slice("uid: '".length,tmp.length-1);
					_dbUID = parseInt(uids);
				}else{
					Logger.getInstance().logError("cannot fine uid when logging in Douban Radio web")
				}
				//GET THE CHANNELS
				DBRadioChannelParser.parseChannels(html);
			}else{
				Logger.getInstance().logError("cannot fine uid and channels for flash player")
			}
		
		}
		
		private var _ck:String;
		private function getCkValue(fmHtml:String):void{
			//http://www.douban.com/accounts/logout?source=radio&ck=fcwo&no_login=y
			
			var rst:Array = fmHtml.match(/http:\/\/www.douban.com\/accounts\/logout\?source=radio&ck=[-\da-zA-Z:+=]*/);
			if(rst!=null && rst.length>0){
				//&ck=[-\da-zA-Z:+=]*
				_ck = (rst[0] as String).slice((rst[0] as String).search("ck=")+3);
			}else{
				Logger.getInstance().logInfo("cannot find ck value");
			}
		}
		
		public function get ck():String{
			return _ck;
		}
		
		private static const LOGIN_ERR_REX:String = '<p class="error">';
		private static const MAX_ERR_LEN:int = 200;
		private var _captchaSrc:String;
		private static const CAPTCHA_REX_STR:String = '<input type="hidden" name="captcha-id" value="';
		private function checkLoginRst(htmlStr:String):int{
			//FIXME:提取错误信息的代码不够准确，目前采用MAX_ERR_LEN来判断有没有被重定向
			htmlStr = htmlStr.slice(htmlStr.search("<body>"),htmlStr.length-1);
			_err = "";
			if(htmlStr.search(/<div class="item-error">/)>=0){
				var substr:String = htmlStr.slice(htmlStr.search(/<p class="error">/g)+LOGIN_ERR_REX.length,
					htmlStr.length);
				_err = substr.slice(0,substr.search(/<\/p/));
			}
			//可能会出现验证码
			var caStartIdx:int = htmlStr.search(/<input type="hidden" name="captcha-id" value="/);
			if(caStartIdx>=0){
				caStartIdx+=CAPTCHA_REX_STR.length;
				var tmp:String = htmlStr.slice(caStartIdx,htmlStr.length);
				_captchaSrc = tmp.slice(0,tmp.search(/"\/>/));
				return -2;
			}else if(_err!="" && _hasRedirected==false){
				return -1;
			}else 
				return 0;
		}
		
		private function errorHandler(e:IOErrorEvent):void{
			_err = e.text;
			dispatchEvent(new DBErrorEvent(DBErrorEvent.LOGIN_FAILED,_err));
		}
		
		private function secrityErrorHandler(e:SecurityErrorEvent):void{
			_err = e.text;
			dispatchEvent(new DBErrorEvent(DBErrorEvent.LOGIN_FAILED,_err));
		}
		
		private var _hasRedirected:Boolean = false;
		private function onHttpStatus(e:HTTPStatusEvent):void{
			if(e.status==403){
				_err = "403 error";
				dispatchEvent(new DBErrorEvent(DBErrorEvent.LOGIN_FAILED,_err));
			}else if(e.status == 302){
				_hasRedirected = true;
			}
		}
		
	}
}